package com.github.jarvett.utils;

import lombok.experimental.UtilityClass;

import javax.net.ssl.*;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

/**
 * 该类使用者使用该类方法时，请自行校验入参
 */
@UtilityClass
public class HttpUtil {

    private static final String POST = "POST";
    private static final String GET = "GET";

    public static String sendGet(String urlStr) {
        return sendGet(urlStr, null, false, null, null);
    }

    /**
     * 发生GET请求
     *
     * @param urlStr
     * @param params
     * @return
     */
    public static String sendGet(String urlStr, Map<String, String> params) {
        return sendGet(urlStr, params, false, null, null);
    }

    /**
     * 发送GET请求
     *
     * @param urlStr       请求地址
     * @param params       请求参数
     * @param isNeedEncode 是否需要URL编码(汉字一般需要)
     * @param charset      字符集
     * @param headers      请求头
     * @return
     */
    public static String sendGet(String urlStr, Map<String, String> params, boolean isNeedEncode, String charset,
            Map<String, String> headers) {
        StringBuilder sb = new StringBuilder();
        if (null != params && !params.isEmpty()) {
            if (!urlStr.contains("?")) {
                sb.append(urlStr);
            } else {
                sb.append(urlStr, 0, urlStr.indexOf("?"));
            }
            if (urlStr.lastIndexOf("/") < 8) {
                sb.append("/");
            }
            sb.append("?");
            sb.append(formParams(params, isNeedEncode, charset));
        } else {
            sb.append(urlStr);
        }
        try {
            HttpURLConnection conn = createURLConnection(sb.toString(), GET, headers);
            return fetchResult(conn, charset);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static String sendPostFile(String urlStr, String charset, Map<String, String> params, String FileField,
            String FileName, String ContentType, byte[] Bytes) {
        try {
            String end = "\r\n";
            String twoHyphens = "--";
            String boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW";
            HttpURLConnection conn = createURLConnection(urlStr, POST, null);
            conn.setRequestProperty("Charset", "UTF-8");
            conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);

            StringBuilder sb = new StringBuilder();
            if (null != params && !params.isEmpty()) {
                for (Map.Entry<String, String> entry : params.entrySet()) {
                    sb.append(twoHyphens).append(boundary).append(end);
                    sb.append("Content-Disposition: form-data; name=\"").append(entry.getKey()).append("\"")
                            .append(end);
                    sb.append(end);
                    sb.append(entry.getValue()).append(end);
                }
            }
            sb.append(twoHyphens).append(boundary).append(end);
            sb.append("Content-Disposition: form-data; name=\"").append(FileField).append("\"; filename=\"")
                    .append(FileName).append("\"").append(end);
            sb.append("Content-Type: ").append(ContentType).append(end);
            sb.append(end);
            DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
            dos.write(sb.toString().getBytes(StandardCharsets.UTF_8));
            dos.write(Bytes);
            dos.writeBytes(end);
            dos.writeBytes(twoHyphens + boundary + twoHyphens + end);// 表单结尾
            dos.flush();
            return fetchResult(conn, charset);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static String sendPostXml(String url, String xml) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/xml");
        return sendPost(url, xml, "UTF-8", headers);
    }

    public static String sendPostJson(String url, String json) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        return sendPost(url, json, "UTF-8", headers);
    }

    /**
     * 发送POST请求
     *
     * @param urlStr  请求地址
     * @param data    要POST的数据(JSON格式时需要和请求头配合)
     * @param charset 字符集
     * @param headers 请求头
     * @return
     */
    public static String sendPost(String urlStr, String data, String charset, Map<String, String> headers) {
        PrintWriter pw = null;
        try {
            HttpURLConnection conn = createURLConnection(urlStr, POST, headers);
            pw = new PrintWriter(conn.getOutputStream());
            pw.print(data);
            pw.flush();
            return fetchResult(conn, charset);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (null != pw) {
                pw.close();
            }
        }
    }

    public static byte[] sendForQrCode(String urlStr, String json) {
        PrintWriter pw = null;
        try {
            Map<String, String> headers = new HashMap<>();
            headers.put("Content-Type", "application/json");
            HttpURLConnection conn = createURLConnection(urlStr, POST, headers);
            pw = new PrintWriter(conn.getOutputStream());
            pw.print(json);
            pw.flush();
            String contentType = conn.getContentType();
            InputStream cin;
            try {
                cin = conn.getInputStream();
            } catch (IOException e) {
                cin = conn.getErrorStream();
            }
            if (null == cin) {
                throw new IOException("请求失败,服务器没有响应");
            }
            byte[] dt = toByteArray(cin);
            if (null == contentType || !contentType.startsWith("image")) {
                throw new RuntimeException("返回的不是图片格式:" + new String(dt));
            }
            return dt;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (null != pw) {
                pw.close();
            }
        }

    }

    public static byte[] toByteArray(InputStream input) throws IOException {
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        int n;
        while (-1 != (n = input.read(buffer))) {
            output.write(buffer, 0, n);
        }
        return output.toByteArray();
    }

    /**
     * @param urlStr
     * @param params
     * @return
     */
    public static String sendPost(String urlStr, Map<String, String> params) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
        return sendPost(urlStr, params, true, StandardCharsets.UTF_8.name(), headers);
    }

    /**
     * 发送POST请求
     *
     * @param urlStr       请求地址
     * @param params       要POST的参数,Map形式
     * @param isNeedEncode 是否需要URL编码,(默认的form形式时)
     * @param charset      字符编码
     * @param headers      请求头
     * @return
     */
    public static String sendPost(String urlStr, Map<String, String> params, boolean isNeedEncode, String charset,
            Map<String, String> headers) {
        String data = formParams(params, isNeedEncode, charset);
        return sendPost(urlStr, data, charset, headers);
    }

    /**
     * 组装成k1=v1&k2=v2形式
     *
     * @param params       需要组装的Map
     * @param isNeedEncode 是否需要URL解码
     * @param charset      字符集
     * @return
     */
    public static String formParams(Map<String, String> params, boolean isNeedEncode, String charset) {
        return formParamsCore(params, isNeedEncode, false, charset);
    }

    public static String formSignParams(Map<String, String> params) {
        return formParamsCore(params, false, true, null);
    }

    /**
     * 组装成k1=v1&k2=v2形式
     *
     * @param params           需要组装的Map
     * @param isNeedEncode     是否需要URL解码
     * @param filterEmptyValue v为空的是否排除
     * @param charset          字符集
     * @return
     */
    private static String formParamsCore(Map<String, String> params, boolean isNeedEncode, boolean filterEmptyValue,
            String charset) {
        StringBuilder sb = new StringBuilder();
        if (null == params) {
            return sb.toString();
        }
        for (Map.Entry<String, String> entry : params.entrySet()) {
            if (filterEmptyValue && (null == entry.getValue() || entry.getValue().trim().equals(""))) {
                continue;
            }
            sb.append(entry.getKey());
            sb.append('=');
            if (isNeedEncode) {
                try {
                    sb.append(URLEncoder.encode(entry.getValue(), charset));
                } catch (UnsupportedEncodingException e) {
                    sb.append(entry.getValue());
                }
            } else {
                sb.append(entry.getValue());
            }
            sb.append('&');
        }
        if (sb.length() > 1) {
            sb.deleteCharAt(sb.length() - 1);
        }
        return sb.toString();
    }

    /**
     * 从k1=v1&k2=v2形式的数据解析成Map
     *
     * @param param        字符串,k1=v1&k2=v2形式
     * @param isNeedDecode 是否需要URL解码
     * @param charset      字符集
     * @return
     */
    public static TreeMap<String, String> parseParams(String param, boolean isNeedDecode, String charset) {
        TreeMap<String, String> resMap = new TreeMap<>();
        try {
            if (isNeedDecode) {
                param = URLDecoder.decode(param, charset);
            }
        } catch (Exception ignored) {
        }
        String[] reArray = param.split("&");
        for (String tmp : reArray) {
            int eqIndex = tmp.indexOf("=");
            resMap.put(tmp.substring(0, eqIndex), tmp.substring(eqIndex + 1));
        }
        return resMap;
    }

    private static HttpURLConnection createURLConnection(String urlStr, String method, Map<String, String> headers) {
        HttpURLConnection conn = null;
        try {
            URL url = new URL(urlStr);
            conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(15000);
            conn.setUseCaches(false);
            if (POST.equals(method)) {
                conn.setDoInput(true);
                conn.setDoOutput(true);
                conn.setRequestMethod(POST);
            }
            if (GET.equals(method)) {
                conn.setRequestMethod(GET);
            }
            conn.setRequestProperty("Accept", "*/*");
            conn.setRequestProperty("Accept-Language", "zh-CN,en-US;q=0.8");
            if (null != headers && !headers.isEmpty()) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    conn.setRequestProperty(entry.getKey(), entry.getValue());
                }
            }
            if ("https".equals(url.getProtocol())) {
                HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
                httpsConn.setSSLSocketFactory(new ASSLSocketFactory());
                httpsConn.setHostnameVerifier(new ASSLSocketFactory.AHostnameVerifier());
                return httpsConn;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return conn;
    }

    private static String fetchResult(HttpURLConnection conn, String charset) throws Exception {
        String line;
        BufferedReader in;
        if (null == charset || "".equals(charset)) {
            charset = "UTF-8";
        }
        StringBuilder sb = new StringBuilder();
        InputStream cin;
        try {
            cin = conn.getInputStream();
        } catch (IOException e) {
            cin = conn.getErrorStream();
        }
        if (null == cin) {
            throw new IOException("请求失败,服务器没有响应");
        }
        in = new BufferedReader(new InputStreamReader(cin, charset));
        while (null != (line = in.readLine())) {
            sb.append(line);
        }
        in.close();
        return sb.toString();
    }

    /**
     * 使用p12证书请求,微信支付要调用
     *
     * @param urlStr
     * @param data
     * @param password
     * @param p12Data
     * @return
     */
    public static String sendPostTSL(String urlStr, String data, String password, byte[] p12Data) {
        PrintWriter pw = null;
        try {
            URL url = new URL(urlStr);
            HttpsURLConnection httpsConn = (HttpsURLConnection) url.openConnection();
            httpsConn.setUseCaches(false);
            httpsConn.setConnectTimeout(5000);
            httpsConn.setReadTimeout(15000);
            httpsConn.setDoInput(true);
            httpsConn.setDoOutput(true);
            httpsConn.setRequestMethod(POST);
            httpsConn.setRequestProperty("Accept", "*/*");
            httpsConn.setRequestProperty("Accept-Language", "zh-CN,en-US;q=0.8");
            SSLContext sslContext = SSLContext.getInstance("SSL");
            KeyStore truststore = KeyStore.getInstance("JKS");
            truststore.load(new ByteArrayInputStream(p12Data), password.toCharArray());
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
            keyManagerFactory.init(truststore, password.toCharArray());
            sslContext.init(keyManagerFactory.getKeyManagers(),
                    new TrustManager[] { ASSLSocketFactory.AnyX509TrustManager.manager },
                    new java.security.SecureRandom());
            httpsConn.setSSLSocketFactory(sslContext.getSocketFactory());
            httpsConn.setHostnameVerifier(new ASSLSocketFactory.AHostnameVerifier());
            pw = new PrintWriter(httpsConn.getOutputStream());
            pw.print(data);
            pw.flush();
            return fetchResult(httpsConn, null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (null != pw) {
                pw.close();
            }
        }
    }

    private static class ASSLSocketFactory extends SSLSocketFactory {

        private SSLContext sslContext;

        private SSLContext createSSLContext() {
            SSLContext sslContext = null;
            try {
                sslContext = SSLContext.getInstance("SSL");
                sslContext.init(null, new TrustManager[] { AnyX509TrustManager.manager }, null);
            } catch (KeyManagementException | NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            return sslContext;
        }

        private SSLContext getSSLContext() {
            if (null == sslContext) {
                sslContext = createSSLContext();
            }
            return this.sslContext;
        }

        @Override
        public String[] getDefaultCipherSuites() {
            return new String[0];
        }

        @Override
        public String[] getSupportedCipherSuites() {
            return new String[0];
        }

        @Override
        public Socket createSocket(Socket socket, String s, int i, boolean b) throws IOException {
            return getSSLContext().getSocketFactory().createSocket(socket, s, i, b);
        }

        @Override
        public Socket createSocket(String s, int i) throws IOException {
            return getSSLContext().getSocketFactory().createSocket(s, i);
        }

        @Override
        public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) throws IOException {
            return getSSLContext().getSocketFactory().createSocket(s, i, inetAddress, i1);
        }

        @Override
        public Socket createSocket(InetAddress inetAddress, int i) throws IOException {
            return getSSLContext().getSocketFactory().createSocket(inetAddress, i);
        }

        @Override
        public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1)
                throws IOException {
            return getSSLContext().getSocketFactory().createSocket(inetAddress, i, inetAddress1, i1);
        }

        private static class AnyX509TrustManager implements X509TrustManager {
            static AnyX509TrustManager manager = new AnyX509TrustManager();

            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

            }

            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        }

        static class AHostnameVerifier implements HostnameVerifier {
            @Override
            public boolean verify(String s, SSLSession sslSession) {
                // 解决由于服务器证书问题导致HTTPS无法访问的情况
                return true;
            }
        }
    }

}
